﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Office.Project.Server.Library;
using System.IO;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Reflection;
using System.Data;
using System.Diagnostics;
using System.Globalization;

namespace Microsoft.Office.Project.EPMAuditing.Library
{
    [Serializable]
    [XmlRootAttribute("ChangeRecord", Namespace = "", IsNullable = false)]
    public class ChangeRecord
    {
        [NonSerializedAttribute]
        private Tracing _tracing = null;
        [NonSerializedAttribute]
        private const int XmlIndentOffset = 2;
        [NonSerializedAttribute]
        private int _setFlag = 0;

        private DateTime _eventTime;
        private string _eventSourceName;
        private string _eventName;
        private PSContextInfo _eventContextInfo;
        private object _psEventArgs;
        private string _dataSetChanges;

        public ChangeRecord() { }

        public ChangeRecord(DateTime eventTime, string eventSourceName, string eventName, PSContextInfo eventContextInfo,
                            object projectServerEventArgs, string dataSetChanges)
            : this(eventTime, eventSourceName, eventName, eventContextInfo, projectServerEventArgs)
        {
            DataSetChanges = dataSetChanges;
        }

        public ChangeRecord(DateTime eventTime, string eventSourceName, string eventName, PSContextInfo eventContextInfo,
                            object projectServerEventArgs)
        {
            EventTime = eventTime;
            EventName = eventName;
            EventSourceName = eventSourceName;
            EventContextInfo = eventContextInfo;
            PSEventArgs = projectServerEventArgs;
        }

        public ChangeRecord(MemoryStream ms)
        {
            IFormatter formatter = new BinaryFormatter();
            ChangeRecord cr = (ChangeRecord)formatter.Deserialize(ms);
            EventTime = cr.EventTime;
            EventName = cr.EventName;
            EventSourceName = cr.EventSourceName;
            EventContextInfo = cr.EventContextInfo;
            PSEventArgs = cr.PSEventArgs;
            DataSetChanges = cr.DataSetChanges;
            ms.Close();
        }

        #region Properties

        [XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
        public DateTime EventTime
        {
            get
            {
                return _eventTime;
            }
            set
            {
                _eventTime = value;
                _setFlag |= 1;
            }
        }

        [XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
        public string EventSourceName
        {
            get
            {
                return _eventSourceName;
            }
            set
            {
                _eventSourceName = value;
                _setFlag |= 2;

            }
        }


        [XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
        public string EventName
        {
            get
            {
                return _eventName;
            }
            set
            {
                _eventName = value;
                _setFlag |= 4;
            }
        }

        [XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
        public PSContextInfo EventContextInfo
        {
            get
            {
                return _eventContextInfo;
            }
            set
            {
                _eventContextInfo = value;
                _setFlag |= 8;
            }
        }


        [XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
        public object PSEventArgs
        {
            get
            {
                return _psEventArgs;
            }
            set
            {
                _psEventArgs = value;
                _setFlag |= 16;
            }
        }

        [XmlElementAttribute(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
        public string DataSetChanges
        {
            get
            {
                return _dataSetChanges;
            }
            set
            {
                _dataSetChanges = value;
            }
        }

        [XmlIgnore]
        public bool RecordComplete
        {
            get
            {
                if (_setFlag == 31)
                    return true;
                else
                    return false;
            }
        }

        [XmlIgnore]
        public string PSEventArgsXml
        {
            get
            {
                return EventArgToXml(_psEventArgs);
            }
        }

        [XmlIgnore]
        public Tracing TracingService
        {
            get
            {
                return _tracing;
            }
            set
            {
                _tracing = value;
            }
        }

        #endregion
        
        #region Convert to Other Types

        public MemoryStream ToMemoryStream()
        {
            IFormatter formatter = new BinaryFormatter();
            MemoryStream ms = new MemoryStream();
            formatter.Serialize(ms, this);
            return ms;
        }

        public byte[] ToArray()
        {
            IFormatter formatter = new BinaryFormatter();
            MemoryStream ms = new MemoryStream();
            formatter.Serialize(ms, this);
            byte[] bytes = ms.ToArray();
            ms.Close();
            return bytes;
        }

        public string ToXml()
        {
            return ToXml(0);
        }

        public string ToXml(int indentStart)
        {
            int[] indents = { indentStart, indentStart + XmlIndentOffset, indentStart + (XmlIndentOffset * 2), indentStart + (XmlIndentOffset * 3) };
            string[] indentStrings = { new string((char)' ', indents[0]), new string((char)' ', indents[1]), new string((char)' ', indents[2]), new string((char)' ', indents[3]) };

            StringBuilder sb = new StringBuilder(1000);
            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<ChangeRecord>", indentStrings[0]));
            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<EventTime>{1}</EventTime>", indentStrings[1], EventTime.ToString()));
            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<EventName>{1}</EventName>", indentStrings[1], EventName));
            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<EventSourceName>{1}</EventSourceName>", indentStrings[1], EventSourceName));

            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<EventContextInfo>", indentStrings[1]));
            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<IsWindowsUser>{1}</IsWindowsUser>", indentStrings[2], EventContextInfo.IsWindowsUser.ToString()));
            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<Lcid>{1}</Lcid>", indentStrings[2], EventContextInfo.Lcid.ToString()));
            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<SiteGuid>{1}</SiteGuid>", indentStrings[2], EventContextInfo.SiteGuid.ToString()));
            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<SiteVersion>{1}</SiteVersion>", indentStrings[2], EventContextInfo.SiteVersion.ToString(CultureInfo.InvariantCulture)));
            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<TrackingGuid>{1}</TrackingGuid>", indentStrings[2], EventContextInfo.TrackingGuid.ToString()));
            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<UrlZone>{1}</UrlZone>", indentStrings[2], EventContextInfo.UrlZone.ToString(CultureInfo.InvariantCulture)));
            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<UserGuid>{1}</UserGuid>", indentStrings[2], EventContextInfo.UserGuid.ToString()));
            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<UserName>{1}</UserName>", indentStrings[2], EventContextInfo.UserName));
            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}</EventContextInfo>", indentStrings[1]));

            sb.Append(EventArgToXml(indents[1], PSEventArgs));

            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<DataSetChanges>{1}</DataSetChanges>", indentStrings[1], DataSetChanges));

            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}</ChangeRecord>", indentStrings[0]));

            return sb.ToString();
        }

        #endregion

        #region Event Arguments to Xml

        private string EventArgToXml(object psEventArgs)
        {
            return EventArgToXml(0, psEventArgs);
        }

        private string EventArgToXml(int indentStart, object psEventArgs)
        {
            int[] indents = { indentStart, indentStart + XmlIndentOffset, indentStart + (XmlIndentOffset * 2), indentStart + (XmlIndentOffset * 3) };
            string[] indentStrings = { new string((char)' ', indents[0]), new string((char)' ', indents[1]), new string((char)' ', indents[2]), new string((char)' ', indents[3]) };

            Type psEventArgsType = psEventArgs.GetType();
            StringBuilder sb = new StringBuilder(1000);
            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<PSEventArgs type=""{1}"">", indentStrings[0], psEventArgsType.Name));

            PropertyInfo[] propInfoArray = psEventArgsType.GetProperties();
            foreach (PropertyInfo propInfo in propInfoArray)
            {
                if (propInfo.PropertyType.BaseType == typeof(DataTable))
                {
                    DataTable dt = (DataTable)propInfo.GetValue(psEventArgs, null);
                    if (dt != null)
                        sb.AppendLine(DataTableToXML(indents[1], dt, propInfo.Name));
                    else
                        sb.Append(string.Format(CultureInfo.InvariantCulture, @"{0}<{1}></{1}>", indentStrings[0], propInfo.Name));

                }
                else if (propInfo.PropertyType.BaseType == typeof(DataSet))
                {
                    DataSet ds = (DataSet)propInfo.GetValue(psEventArgs, null);
                    if (ds != null)
                        sb.AppendLine(DataSetToXML(indents[1], propInfo.Name, ds));
                    else
                        sb.Append(string.Format(CultureInfo.InvariantCulture, @"{0}<{1}></{1}>", indentStrings[0], propInfo.Name));
                }
                else if (propInfo.PropertyType.BaseType == typeof(Array))
                {
                    sb.AppendLine(ArrayToXML(indents[1], propInfo, psEventArgs));
                }
                else
                switch (propInfo.PropertyType.ToString())
                {
                    case "System.String":
                        object propValue = propInfo.GetValue(psEventArgs, null);
                        string propString = string.Empty;
                        if (propValue != null)
                            propString = (string)propValue;
                        sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<{1}>{2}</{1}>", indentStrings[1], propInfo.Name, propString));
                        break;
                    case "System.Int32":
                    case "System.Boolean":
                    case "System.DateTime":
                    case "System.Guid":
                        sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<{1}>{2}</{1}>", indentStrings[1], propInfo.Name, propInfo.GetValue(psEventArgs, null).ToString()));
                        break;
                    case "System.Collections.Generic.Dictionary`2[System.Guid,System.String]":
                        sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<{1}s>", indentStrings[1], propInfo.Name));
                        Dictionary<Guid, string> myDictionary = (Dictionary<Guid, string>)propInfo.GetValue(psEventArgs, null);
                        foreach (KeyValuePair<Guid, string> kvp in myDictionary)
                        {
                            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<{1} key=""{2}"">{3}</{1}>", indentStrings[1], propInfo.Name, kvp.Key, kvp.Value));
                        }

                        sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}</{1}s>", indentStrings[1], propInfo.Name));
                        break;
                    default:
                        object propDefValue = propInfo.GetValue(psEventArgs, null);
                        string propDefString = string.Empty;
                        if (propDefValue != null)
                            propDefString = propDefValue.ToString();
                        sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<{1} type=""unknown({2})"">{3}</{1}>", indentStrings[1], propInfo.Name, propInfo.PropertyType.ToString(), propDefString));
                        break;
                }

            }

            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "{0}</PSEventArgs>", indentStrings[0]));

            return sb.ToString();
        }

        private string ArrayToXML(int indentStart, PropertyInfo propInfo, object psEventArgs)
        {
            int[] indents = { indentStart, indentStart + XmlIndentOffset, indentStart + (XmlIndentOffset * 2), indentStart + (XmlIndentOffset * 3) };
            string[] indentStrings = { new string((char)' ', indents[0]), new string((char)' ', indents[1]), new string((char)' ', indents[2]), new string((char)' ', indents[3]) };

            switch (propInfo.PropertyType.ToString())
            {
                case "System.String[]":
                    return ArrayToXML<string>(indentStart, propInfo, psEventArgs);
                case "System.Guid[]":
                    return ArrayToXML<Guid>(indentStart, propInfo, psEventArgs);
                case "System.Int32[]":
                    return ArrayToXML<Int32>(indentStart, propInfo, psEventArgs);
                default:
                    return string.Format(CultureInfo.InvariantCulture, @"{0}<{1} type=""unknown({2})"">{3}</{1}>", 
                        indentStrings[0], propInfo.Name, propInfo.PropertyType.ToString(), propInfo.GetValue(psEventArgs, null).ToString());
            }
        }

        private string ArrayToXML<T>(int indentStart, PropertyInfo propInfo, object psEventArgs)
        {
            int[] indents = { indentStart, indentStart + XmlIndentOffset, indentStart + (XmlIndentOffset * 2), indentStart + (XmlIndentOffset * 3) };
            string[] indentStrings = { new string((char)' ', indents[0]), new string((char)' ', indents[1]), new string((char)' ', indents[2]), new string((char)' ', indents[3]) };

            StringBuilder sb = new StringBuilder(200);
            string rootElement = propInfo.Name;
            string childElement = propInfo.Name;
            if (rootElement.Contains("Array"))
                childElement = childElement.Substring(0, childElement.Length - 5);
            else if (propInfo.Name.EndsWith("s", StringComparison.OrdinalIgnoreCase))
                childElement = childElement.Substring(0, childElement.Length - 1);
            else
                rootElement += "s";

            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<{1}>", indentStrings[0], rootElement));
            foreach (T var in (T[])propInfo.GetValue(psEventArgs, null))
            {
                sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<{1}>{2}</{1}>", indentStrings[1], childElement, var));
            }
            sb.Append(string.Format(CultureInfo.InvariantCulture, @"{0}</{1}>", indentStrings[0], rootElement));

            return sb.ToString();
        }

        private string DataSetToXML(int indentStart, string propInfoName, DataSet ds)
        {
            StringBuilder returnString = new StringBuilder(1000);
            foreach (DataTable dt in ds.Tables)
                returnString.Append(DataTableToXML(indentStart, dt, propInfoName));
            return returnString.ToString();
        }

        private static string DataTableToXML(int indentStart, DataTable dt, string propInfoName)
        {
            int[] indents = { indentStart, indentStart + XmlIndentOffset, indentStart + (XmlIndentOffset * 2), indentStart + (XmlIndentOffset * 3) };
            string[] indentStrings = { new string((char)' ', indents[0]), new string((char)' ', indents[1]), new string((char)' ', indents[2]), new string((char)' ', indents[3]) };

            StringBuilder sb = new StringBuilder(500);

            string rootElement = propInfoName;
            string childElement = propInfoName;
            if (propInfoName.EndsWith("s", StringComparison.OrdinalIgnoreCase))
                childElement = childElement.Substring(0, childElement.Length - 1);
            else
                rootElement += "s";

            sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<{1}>", indentStrings[0], rootElement)); // DataTable

            foreach (DataRow row in dt.Rows)
            {
                sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<{1}>", indentStrings[1], childElement)); // DataRow
                foreach (DataColumn column in dt.Columns)
                {
                    sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}<{1}>{2}</{1}>", indentStrings[1], 
                        column.ToString(), row[column].ToString())); // Column
                }
                sb.AppendLine(string.Format(CultureInfo.InvariantCulture, @"{0}</{1}>", indentStrings[1], childElement)); // DataRow
            }
            sb.Append(string.Format(CultureInfo.InvariantCulture, @"{0}</{1}>", indentStrings[0], rootElement)); // DataTable

            return sb.ToString();
        }

        #endregion

    }
}
